﻿//
// *************************************************************************************
// WP-Framework
// 
// Developed by
// Lukas Kretschmar (lkretschmar.dev@bluewin.ch)
//
// Switzerland, Copyright (c) 2013 lkretschmar
// This content is released under the MIT License (http://opensource.org/licenses/MIT).
//     
// *************************************************************************************
//
//
			

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using KretschIT.WP_Fx.Core.Validation;
using KretschIT.WP_Fx.EasyMoq.Calls.Arguments;
using KretschIT.WP_Fx.EasyMoq.Matchers;

namespace KretschIT.WP_Fx.EasyMoq.Calls.Collections.Items
{
    internal abstract class CallItem : ICallItem
    {
        #region Ctor

        public CallItem(MethodCallExpression call)
        {
            It.IsNotNull(() => call);

            this.Id = Guid.NewGuid();
            this.Call = call;
        }

        #endregion

        public Guid Id { get; private set; }

        protected MethodCallExpression Call { get; private set; }

        public bool Matches(MethodCallExpression methodCall)
        {
            return this.IsMethodCall(methodCall) 
                && this.HasSameName(methodCall)
                && this.IsGenericMethod(methodCall)
                && this.HasSameReturnType(methodCall) 
                && this.HasSameAmountOfArguments(methodCall) 
                && this.HasSameArguments(methodCall);
        }

        private bool IsMethodCall(MethodCallExpression methodCall)
        {
            return methodCall != null;
        }

        private bool HasSameName(MethodCallExpression methodCall)
        {
            return methodCall.Method.Name == this.Call.Method.Name;
        }

        #region Generics

        private bool IsGenericMethod(MethodCallExpression methodCall)
        {
            var areGenericMethods = methodCall.Method.IsGenericMethod == this.Call.Method.IsGenericMethod;
            if (!areGenericMethods)
            {
                return false;
            }
            return this.HasSameGenericArguments(methodCall);
        }


        private bool HasSameGenericArguments(MethodCallExpression methodCall)
        {
            if (!this.Call.Method.IsGenericMethod)
            {
                return true;
            }
            return CompareGenericArguments(methodCall);
        }

        private bool CompareGenericArguments(MethodCallExpression methodCall)
        {
            var gTypes = this.Call.Method.GetGenericArguments();
            var callGTypes = methodCall.Method.GetGenericArguments();
            if (gTypes.Length == callGTypes.Length)
            {
                return this.CompareGenericArguments(gTypes, callGTypes);
            }
            return false;
        }

        private bool CompareGenericArguments(Type[] gTypes, Type[] callGTypes)
        {
            for (int i = 0; i < gTypes.Length; i++)
            {
                if (!gTypes[i].Equals(callGTypes[i]))
                {
                    return false;
                }
            }
            return true;
        }

        #endregion

        private bool HasSameReturnType(MethodCallExpression methodCall)
        {
            return methodCall.Method.ReturnType == this.Call.Method.ReturnType;
        }

        private bool HasSameAmountOfArguments(MethodCallExpression methodCall)
        {
            return methodCall.Arguments.Count == this.Call.Arguments.Count;
        }

        protected abstract bool HasSameArguments(MethodCallExpression methodCall);

        protected bool Compare(IEnumerable<IMatcher> matcher, IEnumerable<IArgument> args)
        {
            for (int i = 0; i < args.Count(); i++)
            {
                var arg = args.ElementAt(i);
                var m = matcher.ElementAt(i);
                if (!m.Matches(arg))
                {
                    return false;
                }
            }
            return true;
        }
    }
}
